from .listener import *

class UrlsListPanelFuncs(UrlsListPanelListener):

    def __init__(self, parent=None):
        super().__init__(parent=parent)

    def _onSearch_relatePath(self) -> List[Url]:
        '''相对路径搜索'''
        search_path = self.searcher.GetValue().strip()
        # 获取现有的所有路由路径
        if not self.full_paths:
            RichMsgDialog.showOkMsgDialog(self, "当前无路由/解析引擎未解析到路由，搜索引擎无法工作！", "警告")
            return []

        # 检查路由是否合法
        match_path = retools.PATT_VALID_PATH.match(search_path)
        if not match_path:
            RichMsgDialog.showOkMsgDialog(self, "无效的搜索路径", "错误")
            return []
        
        all_sp_path = [_.strip() for _ in match_path.group(1).strip().split('/') if _][1:] # 去掉ip的实际参数集合
        len_all_sp_path = len(all_sp_path)
        paths: List[Url] = []
        '''
            匹配原则：
            1、主观上，不去判断 all_sp_path 的正确与否，就算漏参多参，也默认有匹配项（可能实际上一个都匹配不到）
            2、不去管域名是否完全正确，引擎的关注点在相对路由这里
        '''
        for path in self.full_paths:
            temp_sp_list = [_.strip() for _ in path.relate_path.strip().split('/') if _]
            
            len_temp_sp_list = len(temp_sp_list)
            
            if 0 == len_all_sp_path == len_temp_sp_list: # 根路由，直接匹配返回（链式比较）
                paths.append(path)
                break

            if not ((len_temp_sp_list == len_all_sp_path) or ((len_temp_sp_list+1) == len_all_sp_path)):
                # +1 的情况是：GET请求主动传参，如 .../?pk=1&num=2
                continue # 直接跳过

            for i in range(min(len_all_sp_path, len_temp_sp_list)):
                obj_search = all_sp_path[i] # 待匹配的参数
                obj_sys = temp_sp_list[i] # 系统级参数，包含 <int:pk>
                if retools.PATT_URL_ARGS.match(obj_sys):
                    continue # 任意的参数均可（不检查参数类型，本来就含有纠错功能）
                else:
                    if obj_search != obj_sys:
                        break
            else:
                paths.append(path)
                # break # 只允许一条路由匹配（先匹配所有可能项）
        return paths

    def _onSearch_belongApp(self) -> List[Url]:
        '''归属应用程序搜索【精确查找，使用时请注意大小写敏感】'''
        if not self.full_paths:
            RichMsgDialog.showOkMsgDialog(self, "当前无路由/解析引擎未解析到路由，搜索引擎无法工作！", "警告")
            return []

        belong_app = self.searcher.GetValue().strip()
        if not belong_app:
            RichMsgDialog.showOkMsgDialog(self, "请输入按【应用程序名称】检索的依据！", "警告")
            return []

        paths: List[Url] = []
        for path in self.full_paths:
            if belong_app == path.belong_app:
                paths.append(path)
        
        return paths

    def _onSearch_codeAppName(self) -> List[Url]:
        '''应用程序检索名称搜索【精确查找，使用时请注意大小写敏感】'''
        if not self.full_paths:
            RichMsgDialog.showOkMsgDialog(self, "当前无路由/解析引擎未解析到路由，搜索引擎无法工作！", "警告")
            return []

        code_app_name = self.searcher.GetValue().strip()
        if not code_app_name:
            RichMsgDialog.showOkMsgDialog(self, "请输入按【应用程序代码检索名称】检索的依据！", "警告")
            return []

        paths: List[Url] = []
        for path in self.full_paths:
            if code_app_name == path.code_app_name:
                paths.append(path)
        
        return paths

    def _onSearch_urlAlias(self) -> List[Url]:
        '''路由别名搜索【精确查找，使用时请注意大小写敏感】'''
        if not self.full_paths:
            RichMsgDialog.showOkMsgDialog(self, "当前无路由/解析引擎未解析到路由，搜索引擎无法工作！", "警告")
            return []

        alias = self.searcher.GetValue().strip()
        if not alias:
            RichMsgDialog.showOkMsgDialog(self, "请输入按【路由别名】检索的依据！", "警告")
            return []

        paths: List[Url] = []
        for path in self.full_paths:
            if alias == path.alias:
                paths.append(path)
        
        return paths

    def _onSearch_analysisName(self) -> List[Url]:
        '''反向解析名称搜索【精确查找，使用时请注意大小写敏感】'''
        if not self.full_paths:
            RichMsgDialog.showOkMsgDialog(self, "当前无路由/解析引擎未解析到路由，搜索引擎无法工作！", "警告")
            return []

        alias = self.searcher.GetValue().strip()
        if not alias:
            RichMsgDialog.showOkMsgDialog(self, "请输入按【反向解析名称】检索的依据！", "警告")
            return []

        paths: List[Url] = []
        for path in self.full_paths:
            if alias == f"{path.code_app_name}:{path.alias}":
                paths.append(path)
        
        return paths

    def onSearch(self, evt):
        """搜索事件（小型搜索引擎，引擎需要不断优化，目前引擎还比较初级）
        
            按类型搜索，匹配不同的结果
        """
        paths: List[Url] = [] # 最终的结果集

        choice_way = self.choiceSearch.GetString(self.choiceSearch.GetSelection()).strip()
        if BasisAnalysisObj.BASIS_RELATE_PATH.value == choice_way:
            paths = self._onSearch_relatePath()
        elif BasisAnalysisObj.BASIS_WHOLE_PATH.value == choice_way:
            paths = self._onSearch_relatePath() # 全路由搜索暂时用相对路由搜索代替
        elif BasisAnalysisObj.BASIS_BELONG_APP.value == choice_way:
            paths = self._onSearch_belongApp()
        elif BasisAnalysisObj.BASIS_APP_NAME.value == choice_way:
            paths = self._onSearch_codeAppName()
        elif BasisAnalysisObj.BASIS_URL_ALIAS.value == choice_way:
            paths = self._onSearch_urlAlias()
        elif BasisAnalysisObj.BASIS_ANALYSIS_NAME.value == choice_way:
            paths = self._onSearch_analysisName()
        else: # 默认相对路径搜索
            paths = self._onSearch_relatePath()

        self.treeListData.ClearColumns()
        app = App(f'匹配结果（{len(paths)}个近似匹配）')
        show_nodes: List[App] = [app,]
        show_nodes[0].urls.extend(paths)
        self._init_listree_data(show_nodes)
        self.Layout()

    def onItemDBClick(self, evt):
        """行双击事件"""
        item = evt.GetItem()
        if self.treeListData.IsExpanded(item):
            self.treeListData.Collapse(item)
        else:
            self.treeListData.Expand(item)

    def onItemRightClick(self, e):
        """右击弹出菜单"""
        menu = wx.Menu()
        menu.Append(self.popup_open_url, "在浏览器中打开")
        menu.AppendSeparator()

        menuVSCode = wx.Menu()
        menuVSCode.Append(self.popup_open_view_path, "打开views文件")
        menuVSCode.Append(self.popup_open_url_path, "打开urls文件")
        menuVSCode.Append(self.popup_open_model_path, "打开models文件")
        menuVSCode.Append(self.popup_open_template_path, "打开template文件")
        menu.Append(wx.ID_ANY, "&Visual Studio Code", menuVSCode)

        menu.AppendSeparator()
        menu.Append(self.popup_modify_url, "修改路由路径")
        menu.AppendSeparator()
        menu.Append(self.popup_get_code, "URL的反向解析")
        menu.Append(self.popup_insert_func, "插入功能")
        menu.AppendSeparator()
        menu.Append(self.popup_show_mvc_mtv, "MVC/MTV数据流概览")
        self.PopupMenu(menu)
        menu.Destroy()

    def onItemSelect(self, e):
        """行选择"""
        obj: wxdv.DataViewItem = e.GetItem() # 主要用于检测行是否正确选中数据行
        if obj.IsOk():
            self.select_row = self.model.GetRowDictValue(obj)
        else:
            self.select_row = {}

    def _refresh(self):
        """刷新界面数据"""
        self.treeListData.ClearColumns()
        self._init_listree_data()
        self.Layout()

        env_python3 = env.getPython3Env()
        if not os.path.exists(env_python3):
            RichMsgDialog.showOkMsgDialog(self, "检测到虚拟环境未绑定，数据显示可能不准确，请绑定后返回本界面刷新显示", "警告")

    def onBtnRefresh(self, e):
        """刷新界面"""
        self._refresh()

    def onPopupOpenUrl(self, e):
        """在浏览器中打开"""
        '''
            由于一个方法或类里可以同时存在多个请求方式，所以并不会在一开始解析时就定性，而是需要选择在此界面手动选择
        '''
        way = RichMsgDialog.showSingleChoiceDialog(self, "选择请求方式", "请求", ['GET', 'POST',])
        if way is None:
            return
        
        full_path = self.select_row.get("full_path")
        # 检查全路径是否包含参数（若包含，则弹出参数值填写界面）
        args: List[Tuple[str]] = retools.PATT_URL_ARGS.findall(full_path)
        if 0 == len(args): # 无参直接打开
            if 'GET' == way:
                import webbrowser
                webbrowser.open(full_path)
            else:
                RichMsgDialog.showOkMsgDialog(self, "目前仅实现GET的相关操作", "提示")
        else:
            if 'GET' == way:
                args_v = dialogMsg.showArgsEditDialog(self, args)
                if args_v:
                    new_path = retools.patt_template_replace2(retools.PATT_URL_ARGS, full_path, **args_v)
                    import webbrowser
                    webbrowser.open(new_path)
                else:
                    RichMsgDialog.showOkMsgDialog(self, f"您已取消打开{full_path}", "提示")
            else:
                RichMsgDialog.showOkMsgDialog(self, "目前仅实现GET的相关操作", "提示")

    def onPopupOpenViewPath(self, e):
        """打开views文件"""
        if self.select_row: # 正确选中行，并有数据
            '''
                尝试通过【代码生成方式】提取正确的视图名称
            '''
            t_way = self.select_row['gen_way'].strip() # 代码生成方式（如：模块.视图名）
            left_code = t_way.find("(")
            if -1 == left_code: t_way = t_way.split(".") # 函数视图
            else: t_way = t_way[:left_code].split(".") # 类视图
            if len(t_way) >= 1:
                if "as_view" == t_way[-1].strip(): t_way = t_way[-2].strip()
                else: t_way = t_way[-1].strip()
            else: t_way = ""

            row, col, view_path = djangotools.get_location_from_viewspy(self.select_row['belong_app'], t_way) # 光标定位
            if view_path:
                cmd = f"code -g {view_path}:{row}:{col}" # 原生命令打开VSCode
                wx.Shell(cmd)
            else:
                RichMsgDialog.showOkMsgDialog(self, "找不到匹配项", "警告")

    def onPopupOpenUrlPath(self, e):
        """打开urls文件"""
        if self.select_row:
            row, col = 1, 0 # 光标定位：第一行，第一个字符之前
            cmd = f"code -g {self.select_row['app_file']}:{row}:{col}"
            wx.Shell(cmd)
        else:
            RichMsgDialog.showOkMsgDialog(self, '未正确选中行，请左键点击后再点击右键', '错误')


    def onPopupOpenTemplatePath(self, e):
        """打开template文件"""
        
    def onPopupModifyUrl(self, e):
        """修改路由路径"""
        if self.select_row:
            flag = []
            dlg = ModifyUrlPathDialog(self, self.select_row, self.split_urls, self.urls_data, flag)
            dlg.ShowModal()
            dlg.Close(True)
            if len(flag) > 0: # 修改成功才刷新【存在 BUG，后期解决】
                self._refresh()
        else:
            RichMsgDialog.showOkMsgDialog(self, '未正确选中行，请左键点击后再点击右键', '错误')

    def onPopupInsertFunc(self, e): """插入功能"""

    def onPopupGetCode(self, e):
        """URL的反向解析"""
        if self.select_row:
            dlg = ListTypeCodes(self, self.select_row)
            dlg.ShowModal()
            dlg.Close(True)
        else:
            RichMsgDialog.showOkMsgDialog(self, '未正确选中行，请左键点击后再点击右键', '错误')
        
    def onPopupShowMvcMtv(self, e): """MVC/MTV数据流概览"""
